#include <ctime>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <list>

#include "GLee.h"
#include <GL/glu.h>

#include <boost/function.hpp>
#include <boost/bind.hpp>

#include "Game.h"
#include "MyWindow.h"
#include "Background.h"
#include "HUD.h"
#include "PauseScreen.h"
#include "TextureManager.h"
#include "MainMenu.h"
#include "RedBirdFactory.h"
#include "YellowBirdFactory.h"
#include "BoomerangBirdFactory.h"
#include "WaveFileDescriptor.h"
#include "GameOverScreen.h"
#include "AudioSource.h"
#include "GameOptions.h"
#include "GLTexture.h"
#include "Blade.h"

#define COMBO_GAP   500     // In milliseconds

using namespace std;
using namespace boost::posix_time;

Game::Game(MyWindow* window, const GameMode mode)
: Scene(window),
m_gameMode(mode),
m_background(NULL),
m_hud(NULL),
m_pauseScreen(NULL),
m_elapsedTime(0.0f),
m_isGamePaused(false),
m_score(0),
m_redBirdFactory(NULL),
m_yellowBirdFactory(NULL),
m_boomerangBirdFactory(NULL),
m_bgm(NULL),
m_blade(NULL),
m_combo(0),
m_timeLastExplosion(microsec_clock::local_time())
{
    m_waveList.clear();
}

Game::~Game() 
{
}

bool Game::init()
{
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);

    // Pre-load big textures
    TextureManager* manager = TextureManager::getInstance();
    if (!manager->hasTexture("data/textures/ingamebirds1.tga"))
    {
        manager->addTexture(boost::shared_ptr<GLTexture>(new GLTexture("data/textures/ingamebirds1.tga", GL_LINEAR, 
                                      GL_LINEAR_MIPMAP_LINEAR, GL_RGB8, GL_RGB,
                                      GL_UNSIGNED_BYTE, true)));
    }
    if (!manager->hasTexture("data/textures/ingamebirds1.tga"))
    {
        manager->addTexture(boost::shared_ptr<GLTexture>(new GLTexture("data/textures/levelselection1.tga", GL_LINEAR, 
                                      GL_LINEAR_MIPMAP_LINEAR, GL_RGB8, GL_RGB,
                                      GL_UNSIGNED_BYTE, true)));
    }

    m_background = new Background("data/textures/background1.tga");
    if (!m_background->initialize()) {
        std::cerr << "Could not initialize the background" << std::endl;
        return false;
    }

    m_hud = new HUD(boost::shared_ptr<Game>(this));
    if (!m_hud->initialize()) {
        std::cerr << "Could not initialize the HUD" << std::endl;
        return false;
    }

    m_pauseScreen = new PauseScreen(boost::shared_ptr<Scene>(this));
    if (!m_pauseScreen->initialize()) {
        std::cerr << "Could not initialize the Pause Screen" << std::endl;
        return false;
    }

    // Number of lifes   
    if (m_gameMode == ARCADE)
    {
        GameOptions* globalOpt = GameOptions::getOptions();
        switch(globalOpt->difficulty())
        {
        case EASY:
            m_lifes = 9;
            break;
        case MEDIUM:
            m_lifes = 5;
            break;
        case HARD:
            m_lifes = 3;
            break;
        default:
            break;
        }
    }
    else
    {
        m_lifes = 0;
    }

    // Create all factories
    m_redBirdFactory = new RedBirdFactory(boost::shared_ptr<Scene>(this));
    m_yellowBirdFactory = new YellowBirdFactory(boost::shared_ptr<Scene>(this));
    m_boomerangBirdFactory = new BoomerangBirdFactory(boost::shared_ptr<Scene>(this));

    // Process wave file
    WaveFileDescriptor file;
    file.parse("data/conf/wave_list.xml");
    m_waveList = file.getWaveList();

    // Initialize random seed
    srand((unsigned int)time(0));

    GameOptions* globalOpt = GameOptions::getOptions();
    if (m_gameMode == SURVIVAL)
    {
        switch(globalOpt->difficulty())
        {
        case EASY:
            m_remainingTime = 60.0f * 5; //5 minutes
            break;
        case MEDIUM:
            m_remainingTime = 60.0f * 3; //3 minutes
            break;
        case HARD:
            m_remainingTime = 60.0f * 1; //1 minute
            break;
        default:
            break;
        }
    }
    else
    {
        m_remainingTime = 0.0f;
    }

    // Initialize the background music
    m_bgm = new AudioSource(string("bgm_game"), MUSIC);
    m_bgm->setAudioFile("data/audio/ambient_game.ogg");
    m_bgm->setLoop(true);
    m_bgm->initialize();
    m_bgm->setVolume(globalOpt->musicLevel());
    m_bgm->play();

    // Initialize the blade
    m_blade = new Blade(boost::shared_ptr<Scene>(this));
    if (!m_blade->initialize())
    {
        std::cerr << "Could not initialize the Exit Button" << std::endl;
        return false;
    }

    //Return success
    return true;
}

void Game::prepare(float dt)
{   
    if (!m_isGamePaused)
    {
        //Update the time since we started
        m_elapsedTime += dt;

        // Update remaining time in SURVIVAL mode
        if (m_gameMode == SURVIVAL)
        {
            m_remainingTime -= dt;

            // Check game over condition
            if (m_remainingTime <= 0.0f)
            {
                m_nextScene = new GameOverScreen(m_window, SURVIVAL);
                m_bgm->stop();
            }
        }

        // Check for Pause button pressed to pause the game
        if (m_hud->isPauseButtonPressed())
        {
            m_isGamePaused = !m_isGamePaused;
            m_hud->resetPauseButton();
            return;
        }

        // Check if a new wave of entites must be created
        processWave();

        // Update the HUD
        m_hud->prepare(dt);

        // Update the blade
        m_blade->prepare(dt);

        // Update all entities
        for (EntityIterator entity = m_entities.begin(); entity != m_entities.end(); ++entity)
        {
            (*entity)->prepare(dt);
        }

        // Remove dead entities
        clearDeadEntities();

    } // if (!m_isGamePaused)
    else
    {
        m_pauseScreen->prepare(dt);
        if (m_pauseScreen->resumeButtonPressed())
        {
            m_isGamePaused = !m_isGamePaused;
            m_pauseScreen->resetResumeButton();
        }
        else if (m_pauseScreen->mainButtonPressed())
        {
            m_nextScene = new MainMenu(m_window);
            m_bgm->stop();
        }
        else if (m_pauseScreen->exitButtonPressed())
        {
            shutdown();
        }
    }

    // Reset combo value if needed
    ptime now = microsec_clock::local_time();
    if (millisec(COMBO_GAP) < (now - m_timeLastExplosion))
    {
        m_combo = 0;
        m_timeLastExplosion = now;
    }
}

void Game::render()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //Load the identity matrix (reset to the default position and orientation)
    glLoadIdentity();

    // Render background
    m_background->render();

    // Render the blade
    m_blade->render();

    // Render entities
    for (ConstEntityIterator entity = m_entities.begin(); entity != m_entities.end(); ++entity)
    {
        (*entity)->render();
        (*entity)->postRender();
    }

    // Render HUD
    m_hud->render();

    if (m_isGamePaused)
    {
        m_pauseScreen->render();
    }
}

void Game::shutdown()
{
    //m_window->destroy();
    m_bgm->stop();
    m_nextScene = NULL;
}

void Game::onResize(int width, int height)
{
    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glOrtho(0, width, 0, height, -1, 1);
    m_screenSize.width = static_cast<float> (width);
    m_screenSize.height = static_cast<float> (height);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void Game::increaseScore(const int delta)
{
    m_score += delta;

    // Check combo value
    ptime now = microsec_clock::local_time();
    m_combo++;
    if (millisec(COMBO_GAP) < (now - m_timeLastExplosion))
    {
        m_combo = 1;
    }
    m_timeLastExplosion = now;
}

void Game::decreaseScore(const int delta)
{
    int newScore = m_score - delta;
    if (newScore >= 0)
    {
        m_score = newScore;
    }
    else
    {
        m_score = 0;
    }
}

bool Game::isEntityBladed(const Vector3& position, const Dimension& size) const
{
    return m_blade->isBladed(position, size);
}

void Game::decreaseLifes(const int num)
{
    if (m_gameMode == ARCADE)
    {
        m_lifes -= num;
        if (m_lifes <= 0)
        {
            m_lifes = 0;
            // Game over condition in ARCADE mode
            m_bgm->stop();
            m_nextScene = new GameOverScreen(m_window, ARCADE);
        }
    }
}

void Game::processWave(void)
{
    if (!m_waveList.empty())
    {
        float t = (m_waveList.front()).getTimeOffset();
        if (m_elapsedTime >= t)
        {
            // Create a list of function pointers
            list<boost::function<Entity*(void)>> factories;

            // Red Birds
            if ((m_waveList.front()).getRedBirds() > 0)
            {
                factories.insert(factories.end(), 
                                 (m_waveList.front()).getRedBirds(), 
                                 boost::bind(&RedBirdFactory::createEntity, m_redBirdFactory, Vector3(), Dimension()));
            }

            // Yellow Birds
            if ((m_waveList.front()).getYellowBirds() > 0)
            {
                factories.insert(factories.end(), 
                                 (m_waveList.front()).getYellowBirds(), 
                                 boost::bind(&YellowBirdFactory::createEntity, m_yellowBirdFactory, Vector3(), Dimension()));
            }

            // Boomerang Birds
            if ((m_waveList.front()).getBoomerangBirds() > 0)
            {
                factories.insert(factories.end(), 
                                 (m_waveList.front()).getBoomerangBirds(), 
                                 boost::bind(&BoomerangBirdFactory::createEntity, m_boomerangBirdFactory, Vector3(), Dimension()));
            }

            // Delete processed wave
            m_waveList.pop_front();

            // Process the list of function pointers
            list<boost::function<Entity*(void)>>::iterator it;
            for (it=factories.begin(); it!=factories.end(); it++)
            {
                Entity* newEntity = (*it)();
                Dimension screenSize = getScreenSize();
                float x0 = (rand() / ((float)RAND_MAX + 1)) * screenSize.width;
                float y0 = 0.0f;
                newEntity->setPosition(Vector3(x0, y0, 0.0f));

                // Initialize the new entity
                if (!newEntity->initialize())
                {
                    delete newEntity;
                    std::cerr << "Could not initialize the new entity" << std::endl;
                }
                else
                {        
                    registerEntity(newEntity);
                }
            }
        } // if (m_elapsedTime >= t)
    } // if (!m_waveList.empty())
}
